package semant.secondpass;

import semant.Env;
import semant.TypeChecker;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.VarInfo;
import syntaxtree.ClassDecl;
import syntaxtree.MethodDecl;
import syntaxtree.Type;
import syntaxtree.VisitorAdapter;
import util.List;

public class MethodHandler extends VisitorAdapter
{
	private Env e;
	private ClassInfo ci;
	
	private MethodHandler(Env e, ClassInfo ci)
	{
		this.e = e;
		this.ci = ci;
	}
	
	public static void secondPass(Env e, ClassInfo ci, List<MethodDecl> methodList)
	{
		MethodHandler h = new MethodHandler(e, ci);
		
		for (List<MethodDecl> l = methodList; l != null; l = l.tail)
			l.head.accept(h);
	}
	
	@Override
	public void visit(MethodDecl node)
	{
		MethodInfo mi = ci.methods.get(Symbol.symbol(node.name.s));			
		Type returnType = ExpHandler.secondPass(e, ci, mi, node.returnExp);		

		if (!TypeChecker.compareType(mi.type, returnType, e))
		{
			e.err.Error(node, new Object[]{"Tipo de retorno inválido para método \""+
					mi.decorateName() + "\".",
					"Esperado: " + mi.type,
					"Encontrado: " + returnType});	
		}
				
		validaSobrecarga(node, mi);
		
		StatementHandler.secondPass(e, ci, mi, node.body);
	}
	
	private void validaSobrecarga(MethodDecl node, MethodInfo mi)
	{
		MethodInfo bmi;
		
		// se base foi definida e metodo eh reimplementacao de definicao do pai
		if (ci.base != null && (bmi = ci.base.methods.get(mi.name)) != null)
		{
			// verificamos se tipo de retorno eh igual
			if (!bmi.type.isComparable(mi.type))
			{
				e.err.Error(node, new Object[]{"Reimplementação de método \"" +
						mi.decorateName() + 
						"\" não mantém tipo de retorno de implementação pai.",
						"Esperado (retorno do pai): " + bmi.type,
						"Encontrado (retorno da reimplementacao): " + mi.type});	
			}
			
			int miArgNum = mi.formals == null ? 0 : mi.formals.size();
			int bmiArgNum = bmi.formals == null ? 0 : bmi.formals.size();
			
			// verificamos se numero de parametros eh igual
			if (bmiArgNum != miArgNum)
			{
				e.err.Error(node, new Object[]{"Reimplementação de método \"" +
						mi.decorateName() + 
						"\" difere em número de argumentos de implementação pai.",
						"Nro argumentos esperado: " + bmiArgNum,
						"Nro argumentos encontrado: " + miArgNum});
			}
			else if (miArgNum > 0)
			{
				// verificamos se argumentos batem apenas se ambos tem mesmo nro de argumentos
				// e esse valor eh maior que zero
				List<VarInfo> l = mi.formals;
				int i = 0;
				for (List<VarInfo> bl = bmi.formals; bl != null; bl = bl.tail)
				{
					if (!bl.head.type.isComparable(l.head.type))
					{
						e.err.Error(node, new Object[]{"Argumento " + i + 
								" na reimplementação de método \"" +
								mi.decorateName() + 
								"\" difere em tipo de implementação pai.",
								"Esperado: " + bl.head.type,
								"Encontrado: " + l.head.type});
						break;
					}
					
					l = l.tail;
				}
			}
		}
	}
}
